package com.jialiang.yojcodesandbox.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.dfa.FoundWord;
import cn.hutool.dfa.WordTree;
import com.jialiang.yojcodesandbox.enums.ExecuteCodeStatusEnum;
import com.jialiang.yojcodesandbox.enums.JudgeInfoMessageEnum;
import com.jialiang.yojcodesandbox.model.*;
import com.jialiang.yojcodesandbox.utils.ProcessUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import static com.jialiang.yojcodesandbox.constant.CodeBlackList.JAVA_SENSITIVE_WORD_LIST;

/**
 * 代码沙箱模板
 * 定义核心流程（保存文件、编译代码、执行代码、收集结果、文件清理、错误处理）
 * 子类可以复用默认实现，也可以重写某一个执行环节
 */
@Slf4j
public abstract class CodeSandboxTemplate implements CodeSandbox {

    // 编程语言前缀
    public String prefix;

    // 代码存放的根目录
    public String globalCodeDirPath;

    // 用户代码文件
    public String globalCodeFileName;

    // 超时时间
    private static final long TIME_OUT = 5000L;

    /**
     * 字典树，存放黑名单
     * todo 应该提前校验
     */
    private static final WordTree WORD_TREE;

    static {
        WORD_TREE = new WordTree();
        WORD_TREE.addWords(JAVA_SENSITIVE_WORD_LIST);
    }

    /**
     * 每个实现类必须实现编译以及运行的cmd
     * @param userCodeParentPath 代码所在的父目录
     * @param userCodePath       代码所在目录
     * @return
     */
    public abstract CodeSandboxCmd getCmd(String userCodeParentPath, String userCodePath);

    /**
     * 执行代码
     * @param executeCodeRequest 代码沙箱请求数据
     * @return 沙箱响应数据
     */
    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {

        // 用户代码
        String code = executeCodeRequest.getCode();
        // 用例输入
        List<String> inputList = executeCodeRequest.getInputList();

        // 检测代码中是否包含敏感词
        FoundWord foundWord = WORD_TREE.matchWord(code);
        if (foundWord != null) {
            log.info("代码包含敏感词：{}", foundWord.getFoundWord());
            // 包含敏感词，返回错误信息
            return ExecuteCodeResponse
                    .builder()
                    .status(ExecuteCodeStatusEnum.ERROR.getValue())
                    .judgeInfo(new JudgeInfo(JudgeInfoMessageEnum.DANGEROUS_OPERATION.getValue(), "代码包含敏感词：" + foundWord.getFoundWord(), null, null))
                    .build();
        }

        // 第一步：把用户代码保存为文件，到项目根目录的 tmpCode 目录下
        File userCodeFile = saveCodeToFile(code);
        log.info("用户代码文件存放目录：{}", userCodeFile);

        // 获取编译和运行 cmd 命令
        String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();
        CodeSandboxCmd cmdFromLanguage = getCmd(userCodeParentPath, userCodeFile.getAbsolutePath());
        String compileCmd = cmdFromLanguage.getCompileCmd();
        String runCmd = cmdFromLanguage.getRunCmd();

        // 第二步：编译代码，得到 class 文件
        ExecuteMessage compileFileExecuteMessage = compileFile(compileCmd);
        // 代码编译错误，返回错误信息
        if (compileFileExecuteMessage.getExitCode() != 0) {
            FileUtil.del(userCodeParentPath);
            return ExecuteCodeResponse
                    .builder()
                    .status(ExecuteCodeStatusEnum.FAILED.getValue())
                    .judgeInfo(new JudgeInfo(JudgeInfoMessageEnum.COMPILE_ERROR.getValue(), compileFileExecuteMessage.getErrorMessage(), null, null))
                    .build();
        }

        // 第三步：执行代码，得到输出结果
        List<ExecuteMessage> executeMessageList = runFile(runCmd, inputList);
        FileUtil.del(userCodeParentPath);
        // 使用 Stream API 换行打印每个元素
        String joinedMessages = executeMessageList.stream()
                .map(message -> "exitCode = " + message.getExitCode() + ", output = " + message.getOutput() + ", time = " + message.getTime() + "ms, memory = " + message.getMemory() + "KB")
                .collect(Collectors.joining("\n"));
        log.info("代码执行结果：\n{}", joinedMessages);

        // 第四步：整理输出结果
        ExecuteCodeResponse outputResponse = getOutputResponse(executeMessageList);

        // 第五步：文件清理（默认不清理）
        boolean isDelete = false;
        boolean deleteFile = deleteFile(userCodeFile, isDelete);
        log.info("文件 {} 删除{}", userCodeFile.getAbsoluteFile(), deleteFile ? "成功" : "失败");

        return outputResponse;
    }

    /**
     * 1.把用户代码保存为文件
     * @param code 用户代码
     * @return 用户代码文件对象
     */
    public File saveCodeToFile(String code) {
        // 代码存放在当前项目的根路径：..../yoj-code-sandbox/tmpCode
        String globalCodePath = System.getProperty("user.dir") + globalCodeDirPath;
        // tmpCode 根目录不存在则新建
        if (!FileUtil.exist(globalCodePath)) {
            FileUtil.mkdir(globalCodePath);
        }
        // 存放用户代码：区分不同语言
        String userCodeParentPath = globalCodePath + prefix + File.separator + UUID.randomUUID();
        // 包含后缀名 ....../Main.java
        String userCodePath = userCodeParentPath + globalCodeFileName;
        // 返回存放用户代码的文件对象
        return FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
    }

    /**
     * 2.编译代码，得到class文件
     * @param compileCmd 编译命令
     * @return 编译结果
     */
    public ExecuteMessage compileFile(String compileCmd) {
        // 新启动进程用来执行编译命令
        Process compileProcess = null;
        ExecuteMessage executeMessage;
        try {
            compileProcess = Runtime.getRuntime().exec(compileCmd);
            executeMessage = ProcessUtils.handleProcessMessage(compileProcess, "编译");
            log.info("编译成功");
        } catch (IOException e) {
            throw new RuntimeException("IO 异常：" + e.getMessage());
        } finally {
            assert compileProcess != null;
            compileProcess.destroy();
        }
        // 编译程序，返回信息
        return executeMessage;
    }

    /**
     * 3.执行文件，获得执行结果列表
     * @param runCmd    运行命令
     * @param inputList 输入列表
     * @return 程序执行信息列表
     */
    public List<ExecuteMessage> runFile(String runCmd, List<String> inputList) {
        List<ExecuteMessage> executeMessageList = new ArrayList<>(); // 保存输出列表
        // 程序执行是否超时标志
        AtomicBoolean timeout = new AtomicBoolean(false);
        for (String input : inputList) {
            try {
                // 统计运行时间和内存
                long startTime = System.currentTimeMillis(); //开始计时
                long initMemory = getUsedMemory();
                // 开始执行程序
                Process runProcess = Runtime.getRuntime().exec(runCmd);
                // 使用守护线程实现程序执行超时中断，守护线程会在主线程结束后自动退出
                Thread timeoutThread = new Thread(() -> {
                    try {
                        Thread.sleep(TIME_OUT);
                        if (runProcess.isAlive()) {
                            log.info("当前程序运行超时，已中断");
                            timeout.set(true);
                            runProcess.destroy();
                        }
                        executeMessageList.add(
                                ExecuteMessage
                                        .builder()
                                        .exitCode(-10001)
                                        .errorMessage("超时")
                                        .time(TIME_OUT)
                                        .build()
                        );
                    } catch (InterruptedException e) {
                        log.error("程序执行超时，已中断" + e.getMessage());
                    }
                });
                // 设置守护线程
                timeoutThread.setDaemon(true);
                timeoutThread.start();

                // 运行程序，返回信息
                ExecuteMessage executeMessage = ProcessUtils.handleProcessInteraction(runProcess, input, "运行");
                long finalMemory = getUsedMemory();
                long endTime = System.currentTimeMillis();  // 计时结束

                executeMessage.setMemory(((finalMemory - initMemory) / 1024));  // 占用内存，单位：KB
                executeMessage.setTime(endTime - startTime);
                // 保存每一个用例执行信息
                executeMessageList.add(executeMessage);
            } catch (Exception e) {
                getErrorResponse(e);
            }
            // 有一个用例超时则直接退出
            if (timeout.get()) {
                return executeMessageList;
            }
        }
        return executeMessageList;
    }

    /**
     * 4.获取输出结果，返回沙箱响应数据
     * @param executeMessageList 执行信息列表
     * @return 沙箱响应数据
     */
    public ExecuteCodeResponse getOutputResponse(List<ExecuteMessage> executeMessageList) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        // 获取输出列表
        List<String> outputList = new ArrayList<>();
        // 根据用时最大值判断是否超时
        long maxTime = 0;
        long maxMemory = 0;
        for (ExecuteMessage executeMessage : executeMessageList) {
            // 有错误信息
            String errorMessage = executeMessage.getErrorMessage();
            if (StrUtil.isNotBlank(errorMessage)) {
                JudgeInfo judgeInfo = new JudgeInfo();
                judgeInfo.setMessage(JudgeInfoMessageEnum.WRONG_ANSWER.getValue());
                judgeInfo.setErrDetails(errorMessage);
                executeCodeResponse.setJudgeInfo(judgeInfo);
                // 用户提交的代码 执行中存在错误
                executeCodeResponse.setStatus(ExecuteCodeStatusEnum.FAILED.getValue());
                break;
            }
            // 添加正确信息到输出列表中
            outputList.add(executeMessage.getOutput());
            // 程序执行时间
            Long time = executeMessage.getTime();
            if (time != null) {
                // 更新最大值
                maxTime = Math.max(maxTime, time);
            }
            // 程序执行占用内存
            Long memory = executeMessage.getMemory();
            if (memory != null) {
                maxMemory = Math.max(maxMemory, memory);
            }
        }
        // 正常运行完成
        if (outputList.size() == executeMessageList.size()) {
            executeCodeResponse.setStatus(ExecuteCodeStatusEnum.SUCCESS.getValue());
        }
        executeCodeResponse.setOutputList(outputList);
        JudgeInfo judgeInfo = new JudgeInfo();
        judgeInfo.setTime(maxTime);
        judgeInfo.setMemory(maxMemory);
        executeCodeResponse.setJudgeInfo(judgeInfo);

        return executeCodeResponse;
    }

    /**
     * 5.文件清理
     */
    public boolean deleteFile(File userCodeFile, boolean isDelete) {
        if (!isDelete) {
            return false;
        }
        if (userCodeFile.getParentFile() != null) {
            String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();
            return FileUtil.del(userCodeParentPath);
        }
        return true;
    }

    /**
     * 6.获取错误响应，封装一个错误处理类，提升程序健壮性
     */
    private ExecuteCodeResponse getErrorResponse(Throwable e) {
        return ExecuteCodeResponse
                .builder()
                .outputList(new ArrayList<>())
                .status(ExecuteCodeStatusEnum.ERROR.getValue())
                .judgeInfo(new JudgeInfo(JudgeInfoMessageEnum.SYSTEM_ERROR.getValue(), e.getMessage(), null, null))
                .build();
    }

    /**
     * 获取当前已使用的内存量
     * 单位是 byte
     */
    public static long getUsedMemory() {
        Runtime runtime = Runtime.getRuntime();
        return runtime.totalMemory() - runtime.freeMemory();
    }
}
